<!doctype html>
<html>
<head>
<title>Mmvp.js - (&micro;) model view presenter library</title>

<script type='text/javascript' src='underscore.min.js'></script>
<script type='text/javascript' src='Mmvp.js'></script>

<script type='text/javascript' src='js/lib/zepto.js'></script>
<script type='text/javascript' src='js/lib/shCore.js'></script>
<script type='text/javascript' src='js/lib/shBrushJScript.js'></script>
<script type='text/javascript' src='js/todo.js'></script>
<script type='text/javascript' src='js/demo_page.js'></script>

<link rel='stylesheet' href='css/style.css'/>
<link rel='stylesheet' href='css/shCoreDefault.css'/>
</head>

<body>
<main>
<a href="http://github.com/mil/Mmvp.js" target="_blank"><h1>Mmvp.js</h1></a>
<span class='subline'>(&micro;) model view presenter library</span>

<section id="overview">
  <h2>Abstract</h2>
  <p><a href="http://github.com/mil/Mmvp.js" target='_blank'>Mmvp.js</a> is an micro-MVP library which understands the view as ephemeral&hellip; exclusively conceived &amp; manipulated through callbacks.  Accordingly, all your DOM manipulation, templating, &amp; animation logic lives in several presenter callbacks (<code>empty</code>, <code>populate</code>, <code>add</code>, <code>remove</code>, &amp; <code>update</code>).</p>

  <p>When the model is modified, the corresponding presenter callback(s) are triggered. <a href="http://github.com/mil/Mmvp.js" target="_blank">Mmvp.js</a> believes M&rarr;V architecture itself should be completely decoupled from the DOM.  Infact, Mmv(P).js, on its own, does not even know the DOM exists.  Put simply, Mmvp.js is just P framework to bind V-generating callbacks to model changes.</p>
</section>
<section id="demo">
  <h2>Demo</h2>
  <p>Here's the archetypal TodoMVC demo using Mmvp.js. The <a id='todo-src' href="#">demo code</a> (78 SLOC) for it's implementation is slightly heavier than the <a id='mmvp-src' href="#">Mmvp.js framework code</a> itself (46 SLOC).</p>
  <nav><h3 class='view active'>View</h3><h3 class='model'>Model</h3></nav>
  <section id='switcher'> 
    <section class='view active'>  
      <div id="viewpen"></div>
      <div id='newitem'>
        <span class='hint'>&raquo;</span>
        <input type='text' placeholder='Item Name'>
      </div>
    </section>
    <section class='model'><pre class='brush: js; toolbar: false;'></pre></section>
  </section>
</section>


<div id='code'>
  <link rel='stylesheet' href='css/style.css' />
  <link rel='stylesheet' href='css/shCoreDefault.css' />
  <link rel='stylesheet' href='css/source.css' />
  <nav><h3 class='todo'>Todo.js</h3><h3 class='mmvp'>Mmvp.js</h3></nav>
  <section class='source mmvp'>
    <a href="http://raw.githubusercontent.com/mil/Mmvp.js/master/Mmvp.js">raw</a>
    <script type='syntaxhighlighter' class='brush: js; toolbar: false;'><![CDATA[
var Mmvp = (function(my) {
  var actions = {}, model = {};
  _.each(['add', 'remove', 'update', 'populate', 'empty'], function(a) {
    actions[a] = function() {};
  });
  function sync(hash_with_unique_keys) { 
    var add_items = _.omit(hash_with_unique_keys, _.keys(model));
    var remove_items = _.omit(model, _.keys(hash_with_unique_keys));

    _.each(_.pick(model, _.keys(hash_with_unique_keys)), function(v,k) {
      _.each(_.keys(model[k]), function(key) {
        if (model[k][key] != hash_with_unique_keys[k][key]) {
          model[k] = hash_with_unique_keys[k];
          actions['update'](k,model[k]);
          return;
        }
      });
    });

    if (_.size(hash_with_unique_keys) == 0 && _.size(model) != 0) { 
      actions['empty'](); 
    }
    if (_.size(model) == 0 && _.size(hash_with_unique_keys) != 0) { 
      actions['populate'](); 
    }

    _.each(add_items, function(value, key) { actions['add'](key, value); });
    _.each(remove_items, function(value, key) { actions['remove'](key, value) });

    model = JSON.parse(JSON.stringify(hash_with_unique_keys));
    return true;
  }
  return {
    sync       : sync,
    get_model  : function() { return model; },
    initialize : function() { actions.empty(); },
    set_action : function(new_hash) {
      _.each(new_hash, function(v,k) {
        if (actions[k]) { actions[k] = v; }
      });
    }
  };
});
]]></script>
  </section>
  <section class='source todo'>
    <a href="http://raw.githubusercontent.com/mil/Mmvp.js/master/demo/js/todo.js">raw</a>
    <script type='syntaxhighlighter' class='brush: js; toolbar: false;'><![CDATA[
// Onload
$(function() {
  window.todo_model = {}, window.todo_presenter = new Mmvp();
  var presenter_dom_identifier = "#viewpen";


  // Setup User Interface Callbacks
  (function ui_callbacks() {
    // Remove Todo Button
    $(document).on("click", "button.remove", function(ev) {
      delete todo_model[$(ev.target).parent().attr('id')];
      todo_presenter.sync(todo_model);
    });
    // Checkmark Todo
    $(document).on("click", "input[type='checkbox']", function(ev) {
      var is_checked = $(ev.target).is(":checked");
      todo_model[$(ev.target).parent().attr("id")]['checked'] = is_checked;
      todo_presenter.sync(todo_model);
    });
    // Add a New Todo
    var item_add_counter = 0;
    $("input").on("keypress", function(ev) {
      if (ev.which == 13) {
        todo_model[item_add_counter++] = { 
          text    : $("input[type='text']").val(), 
          checked : false 
        };
        $("input").val("");
        todo_presenter.sync(todo_model);
      }
    });
  })();

    
  // Setup Presenter Callbacks
  todo_presenter.set_action({
    empty : function() {
      $("#items-container").remove();
      var no_items_el = $("<div id='no-items'>");
      no_items_el.append("<h4>No Items!</h4>").append("<p>Hint: Add a new item.</p>");
      $(presenter_dom_identifier).append(no_items_el);
      no_items_el.animate({opacity : 1 }, 300, 'ease-in');
    },
    populate : function() {
      $("#no-items").remove(); 
      $(presenter_dom_identifier).append("<div id='items-container'>");
    },
    add : function(new_model_key, new_model_value) {
      var checked = new_model_value == true ? "checked" : "";
      var new_div_el = $("<div>");
      var checkbox = $("<input type='checkbox'>");
      checkbox.attr("checked", (checked ? "" : null));
      var span = $("<span>").text(new_model_value.text);
      var remove_btn = $("<button class='remove'>").html("&#150;");
      new_div_el.attr('id', new_model_key).append(checkbox, span, remove_btn);

      $("#items-container").prepend(new_div_el);
      new_div_el.animate({ opacity: 1 }, 300, 'ease-in');
    },
    remove : function(key_of_removed_item) {
      $("#" + key_of_removed_item).remove();
    },
    update : function(key_of_updated_model, updated_model_value) {
      if (updated_model_value.checked) {
        $("#" + key_of_updated_model).addClass("strikethrough");
      } else {
        $("#" + key_of_updated_model).removeClass("strikethrough");
      }
    }
  });


  // Initialize Mmvp Todo Presenter
  todo_presenter.initialize();
});
]]></script>
  </section>
</div>
</main>

</body>
</html>

